<?php

namespace app\extensions\weixin;

use app\extensions\wechat\crypt\WeChatCrypt;
use app\utils\BaseUtils;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\FileHelper;
use yii\helpers\Json;
use yii\imagine\Image;

class WeiXin extends Component {

    public $token;
    public $appId;
    public $appSecret;

    protected $_data = [];
    protected $apiBaseUrl = 'https://api.weixin.qq.com/cgi-bin';

    public function init() {
        parent::init();
        if (empty($this->appId) || empty($this->token)) {
            throw new InvalidConfigException('微信APPID不能空.');
        }
    }

    private function checkSignature() {
        $signature = isset($_GET['signature']) ? $_GET['signature'] : '';
        $timestamp = isset($_GET['timestamp']) ? $_GET['timestamp'] : '';
        $nonce = isset($_GET['nonce']) ? $_GET['nonce'] : '';
        $token = $this->token;
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);
        return $tmpStr === $signature;
    }

    public function validateRequest() {
        if (!$this->checkSignature()) {
            return FALSE;
        }
        if (isset($_GET['echostr'])) {
            $echoStr = $_GET['echostr'];
            return $echoStr;
        }
        return TRUE;
    }

    public function parseXml() {
        $postStr = file_get_contents("php://input");
        $this->_data = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        return $this;
    }

    public function getRequestData() {
        return $this->_data;
    }

    protected function getValue($field, $default = NULL) {
        if (isset($this->_data[$field])) {
            return $this->_data[$field];
        }
        return $default;
    }

    public function getContent() {
        return $this->getValue('Content', '');
    }

    public function getMsgType() {
        return $this->getValue('MsgType');
    }

    public function getEvent() {
        return $this->getValue('Event', '');
    }

    public function getFromUserName() {
        return $this->getValue('FromUserName');
    }

    public function getToUserName() {
        return $this->getValue('ToUserName');
    }

    public function text($msg) {
        $msg = array(
            'ToUserName' => $this->getFromUserName(),
            'FromUserName' => $this->getToUserName(),
            'MsgType' => 'text',
            'Content' => $msg,
            'CreateTime' => time()
        );
        return $this->reply($msg);
    }

    public function getFileForMediaId($media_id, $is_video = FALSE) {
        $access_token = $this->getAccessToken(TRUE);
        $url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token={$access_token}&media_id={$media_id}";
        if (TRUE === $is_video) {
            $url = str_replace('https', 'http', $url);
        }

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $response = curl_exec($ch);
        curl_close($ch);
        if (!$response) {
            Yii::error("request: {$url}", 'weiXin');
            return NULL;
        }

        list($header, $body) = explode("\r\n\r\n", $response, 2);
        if ($body[0] === '{') {
            Yii::error("body: {$url}", 'weiXin');
            return NULL;
        }

        $path = '/uploads/' . date('Ym') . '/';
        FileHelper::createDirectory(APP_ROOT . $path);

        if (preg_match('/filename="([^"]+(\.[^"]+))"/', $header, $match)) {
            $file = $path . substr(md5($match[1] . uniqid()), 8, 8) . $match[2];
        } else {
            $file = $path . md5(uniqid()) . '.amr';
        }

        @file_put_contents(APP_ROOT . $file, $body);

        $fileAbsolutePath = APP_ROOT . $file;
        BaseUtils::thumb($fileAbsolutePath, 800, 800);

        return $file;
    }

    public function getAccessToken($force = FALSE) {
        $cache = Yii::$app->cache->get('accessToken');
        if ($cache && !$force) {
            return $cache;
        }
        $params = [
            'grant_type' => 'client_credential',
            'appid' => $this->appId,
            'secret' => $this->appSecret
        ];
        $res = $this->sendRequest("GET", 'https://api.weixin.qq.com/cgi-bin/token', $params);
        if ($res['code'] != 0) {
            Yii::error($res['msg'], __CLASS__);
            return FALSE;
        }

        $data = $res['data'];
        if (is_array($data) && isset($data['access_token'])) {
            $accessToken = $data['access_token'];
            Yii::$app->cache->set('accessToken', $accessToken, 3600);
            return $accessToken;
        }
        return FALSE;
    }

    public function getOAuthAccessToken($code) {
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
        $params = [
            'grant_type' => 'authorization_code',
            'appid' => $this->appId,
            'secret' => $this->appSecret,
            'code' => $code,
        ];
        return $this->sendRequest('GET', $url, $params);
    }

    public function getOAuthUserInfo($ACCESS_TOKEN, $openid) {
        $params = ['openid' => $openid, 'access_token' => $ACCESS_TOKEN];
        return $this->sendRequest('GET', 'https://api.weixin.qq.com/sns/userinfo', $params);
    }

    protected function api($apiSubUrl, array $params = [], $method = 'GET', $json = NULL) {
        if (preg_match('/^https?:\\/\\//is', $apiSubUrl)) {
            $url = $apiSubUrl;
        } else {
            $url = $this->apiBaseUrl . '/' . $apiSubUrl;
        }

        $params['access_token'] = $this->getAccessToken();
        if (NULL !== $json) {
            $url = $this->composeUrl($url, $params);
            $params = $json;
        }

        $res = $this->sendRequest($method, $url, $params);
        if (intval($res['code']) === 42001) {
            $params['access_token'] = $this->getAccessToken(TRUE);
            if (NULL !== $json) {
                $url = $this->composeUrl($url, $params);
                $params = $json;
            }
            return $this->sendRequest($method, $url, $params);
        }
        return $res;
    }

    public function sendMessage($content, $openId) {
        $data = [
            'touser' => $openId,
            'msgtype' => 'text',
            'text' => [
                'content' => $content
            ],
        ];
        $json = Json::encode($data);
        return $this->api('message/custom/send', [], 'POST', $json);
    }

    public function sendAll($content) {
        $data = [
            'filter' => [
                'is_to_all' => TRUE,
            ],
            'msgtype' => 'text',
            'text' => [
                'content' => $content
            ],
        ];
        $json = Json::encode($data);
        return $this->api('message/mass/sendall', [], 'POST', $json);
    }

    public function createMenu($data) {
        if (is_array($data)) {
            $data = Json::encode($data);
        }
        return $this->api('menu/create', [], 'POST', $data);
    }

    public function getMenu() {
        return $this->api('menu/get');
    }

    public function getUserInfo($openid) {
        $params = ['openid' => $openid, 'lang' => 'zh_CN'];
        return $this->api('user/info', $params);
    }

    public function getQrCode() {
        $params = [
            'action_name' => 'QR_LIMIT_SCENE',
            'action_info' => [
                'scene' => [
                    'scene_id' => 888
                ]
            ]
        ];
        $json = Json::encode($params);
        return $this->api('qrcode/create', [], 'POST', $json);
    }

    public function getSignPackage() {
        $cacheKey = 'jsApiTicket';
        $jsApiTicket = Yii::$app->cache->get($cacheKey);
        if (!$jsApiTicket) {
            $res = $this->api('ticket/getticket', ['type' => 'jsapi']);
            if ($res['code'] != 0) {
                Yii::error($res['msg'], __CLASS__);
                return [];
            }
            $result = $res['data'];
            $jsApiTicket = $result['ticket'];
            Yii::$app->cache->set($cacheKey, $jsApiTicket, 3600);
        }

        $url = Yii::$app->request->hostInfo . Yii::$app->request->getUrl();
        $timestamp = time();
        $nonceStr = substr(md5(uniqid()), 8, 16);

        $string = "jsapi_ticket=$jsApiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
        $signature = sha1($string);

        return [
            'appId' => $this->appId,
            'nonceStr' => $nonceStr,
            'timestamp' => $timestamp,
            'url' => $url,
            'signature' => $signature,
            'rawString' => $string
        ];
    }

    protected function reply($msg = array()) {
        return $this->xml_encode($msg);
    }

    protected function xmlSafeStr($str) {
        return '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str) . ']]>';
    }

    protected function data_to_xml($data) {
        $xml = '';
        foreach ($data as $key => $val) {
            is_numeric($key) && $key = "item id=\"$key\"";
            $xml .= "<$key>";
            $xml .= (is_array($val) || is_object($val)) ? $this->data_to_xml($val) : $this->xmlSafeStr($val);
            list($key,) = explode(' ', $key);
            $xml .= "</$key>";
        }
        return $xml;
    }

    protected function xml_encode($data, $root = 'xml', $item = 'item', $attr = '', $id = 'id') {
        if (is_array($attr)) {
            $_attr = array();
            foreach ($attr as $key => $value) {
                $_attr[] = "{$key}=\"{$value}\"";
            }
            $attr = implode(' ', $_attr);
        }
        $attr = trim($attr);
        $attr = empty($attr) ? '' : " {$attr}";
        $xml = "<{$root}{$attr}>";
        $xml .= $this->data_to_xml($data);
        $xml .= "</{$root}>";
        return $xml;
    }

    protected function sendRequest($method, $url, $params = [], array $headers = []) {
        $curlOptions = [
            CURLOPT_CONNECTTIMEOUT => 30,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => FALSE,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => TRUE,
            CURLOPT_URL => $url,
        ];
        $curlOptions = $this->composeRequestCurlOptions($method, $url, $params, $curlOptions);

        $curlResource = curl_init();
        foreach ($curlOptions as $option => $value) {
            curl_setopt($curlResource, $option, $value);
        }
        $response = curl_exec($curlResource);
        $responseHeaders = curl_getinfo($curlResource);

        $errorNumber = curl_errno($curlResource);
        $errorMessage = curl_error($curlResource);

        curl_close($curlResource);

        if ($errorNumber > 0) {
            return self::retErr($errorMessage);
        }
        if (strncmp($responseHeaders['http_code'], '20', 2) !== 0) {
            return self::retErr($errorMessage, $responseHeaders['http_code']);
        }

        $response = Json::decode($response);
        if (isset($response['errcode']) && $response['errcode'] > 0) {
            return self::retErr($response['errmsg'], $response['errcode']);
        }
        return self::retOK($response);
    }

    protected function composeRequestCurlOptions($method, $url, $params, $curlOptions) {
        switch ($method) {
            case 'GET':
                $curlOptions[CURLOPT_URL] = $this->composeUrl($url, $params);
                break;
            case 'POST':
                $curlOptions[CURLOPT_POST] = TRUE;
                //$curlOptions[CURLOPT_HTTPHEADER] = ['Content-type: application/x-www-form-urlencoded'];
                $curlOptions[CURLOPT_POSTFIELDS] = is_array($params) && !empty($params) ? http_build_query($params, '', '&', PHP_QUERY_RFC3986) : $params;
                break;
            default:
                $curlOptions[CURLOPT_CUSTOMREQUEST] = $method;
                if (!empty($params)) {
                    $curlOptions[CURLOPT_POSTFIELDS] = $params;
                }
        }
        return $curlOptions;
    }

    protected function composeUrl($url, array $params = []) {
        if (strpos($url, '?') === FALSE) {
            $url .= '?';
        } else {
            $url .= '&';
        }
        $url .= http_build_query($params, '', '&', PHP_QUERY_RFC3986);
        return $url;
    }

    public static function retErr($msg = '', $code = 1, $data = []) {
        if (is_array($msg)) {
            $msg = Json::encode($msg);
        }
        return ['code' => $code, 'msg' => $msg, 'data' => $data];
    }

    public static function retOK($data = [], $msg = '') {
        return ['code' => 0, 'data' => $data, 'msg' => $msg];
    }
}